<template>
  <div class="main-body">
    <el-alert v-show="httpsAlert !== ''" :closable="false" :title="httpsAlert" type="info" />

    <div class="video-container">
      <div style="position: relative">
        <video ref="video" class="radiusDiv" @playing="onVideoPlaying"></video>
        <div class="face-guide-box">
          <div class="guide-border"></div>
          <transition name="fade">
            <div v-if="showPositionTips" class="position-tips">
              {{ positionTipsText }}
            </div>
          </transition>
        </div>
      </div>

      <div class="tips-container">
        <div class="tips-box" :class="{ 'with-position-tips': showPositionTips }">
          {{ identifyMsg }}
        </div>
      </div>

      <!-- 添加浮窗显示人脸图像 -->
      <transition name="fade">
        <div v-if="showFacePhoto" class="floating-box">
          <img alt="Face" class="face-photo" :src="faceImage" />
        </div>
      </transition>
    </div>
  </div>
</template>

<script setup lang="ts">
import * as faceApi from 'face-api.js'
import { debounce, throttle } from 'lodash'
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { faceCheckIn } from '/@/api/faceRecognition'
import request from '/@/utils/request'

import { useRoute } from 'vue-router'

const route = useRoute()
defineOptions({
  name: 'Face',
})

const apiUrl = ref('/api/ossPut')

// 类型定义
interface ApiResponse<T = any> {
  code: number
  message: string
  result?: T
}

interface CheckInResult {
  id: string
  activityId: string
  userId: string
  name: string
  hotelCheckinId: string | null
  mobile: string
  attendMeetingStatus: number
  isCheck: boolean
  checkType: string
  checkTime: string
  isLeave: boolean
  createdAt: string
  updatedAt: string
}

// 配置常量
const CONFIG = {
  CAMERA: {
    WIDTH: 800,
    HEIGHT: 800,
  },
  FACE_DETECTION: {
    MIN_CONFIDENCE: 0.4,
    MAX_RESULTS: 1,
    POSITION_THRESHOLD: 100,
    MIN_FACE_RATIO: 0.05,
    MAX_FACE_RATIO: 0.4,
    PADDING: 80,
  },
  RECOGNITION_INTERVAL: 1000,
  RESET_DELAY: 2000,
}

// 状态与引用
const video = ref<HTMLVideoElement | null>(null)
const identifyMsg = ref('')
const httpsAlert = ref('')
const status = ref<'idle' | 'recognizing' | 'success' | 'error'>('idle')
const isInitialized = ref(false)
const showPositionTips = ref(false)
const positionTipsText = ref('')
const userName = ref('')
const showFacePhoto = ref(false)
const faceImage = ref('')
const successAudio = new Audio('https://qushen-outsourcing.oss-cn-beijing.aliyuncs.com/hongyou/638418eae8107814.mp3')

// 人脸识别节流与防抖
const recognitionThrottle = throttle(async () => await recognizeFace(), CONFIG.RECOGNITION_INTERVAL)
const recognitionDebounce = debounce(() => stateUtils.resetStatus(), CONFIG.RESET_DELAY)

// 状态监听器
watch(status, (newStatus) => {
  console.log(`Status changed to: ${newStatus}`)
  switch (newStatus) {
    case 'idle': {
      identifyMsg.value = '' // 空闲状态显示空文本
      break
    }
    case 'recognizing': {
      identifyMsg.value = '识别中...请保持不动'
      break
    }
    case 'success': {
      identifyMsg.value = `签到成功`
      break
    }
    case 'error': {
      identifyMsg.value = '无权签到'
      break
    }
  }
})

// 摄像头相关函数
const cameraUtils = {
  async start() {
    try {
      console.log('尝试访问摄像头...')
      const stream = await navigator.mediaDevices.getUserMedia({
        video: {
          width: CONFIG.CAMERA.WIDTH,
          height: CONFIG.CAMERA.HEIGHT,
        },
      })
      if (video.value) {
        video.value.srcObject = stream
        video.value.play()
        console.log('摄像头已启动')
      }
      status.value = 'idle'
    } catch (error) {
      console.error('无法访问摄像头:', error)
      httpsAlert.value = '请检查浏览器 HTTPS 设置并重试'
      status.value = 'error'
    }
  },

  stop() {
    if (video.value?.srcObject) {
      const tracks = (video.value.srcObject as MediaStream).getTracks()
      tracks.forEach((track) => track.stop())
      video.value.srcObject = null
      console.log('摄像头已停止')
    }
  },
}

// 人脸识别相关函数
const faceRecognitionUtils = {
  async initialize() {
    try {
      console.log('正在加载模型...')
      await faceApi.nets.ssdMobilenetv1.loadFromUri('/public/admin/models')
      await faceApi.loadFaceLandmarkModel('/public/admin/models')
      isInitialized.value = true
      console.log('模型加载完成')
    } catch (error) {
      console.error('模型加载失败:', error)
      throw new Error('人脸识别模型加载失败')
    }
  },

  async detectFace(videoElement: HTMLVideoElement) {
    const canvas = faceApi.createCanvasFromMedia(videoElement)
    const displaySize = {
      width: videoElement.videoWidth,
      height: videoElement.videoHeight,
    }

    canvas.width = displaySize.width
    canvas.height = displaySize.height

    const context = canvas.getContext('2d')
    if (!context) {
      throw new Error('无法获取 canvas 上下文')
    }

    context.drawImage(videoElement, 0, 0, displaySize.width, displaySize.height)
    const detectionOptions = new faceApi.SsdMobilenetv1Options({
      minConfidence: CONFIG.FACE_DETECTION.MIN_CONFIDENCE,
      maxResults: CONFIG.FACE_DETECTION.MAX_RESULTS,
    })

    const result = await faceApi.detectSingleFace(canvas, detectionOptions)

    if (result) {
      const { box } = result
      const centerX = box.x + box.width / 2
      const centerY = box.y + box.height / 2
      const idealCenterX = displaySize.width / 2
      const idealCenterY = displaySize.height / 2

      // 检查人脸位置并给出提示
      showPositionTips.value = true
      if (Math.abs(centerX - idealCenterX) > CONFIG.FACE_DETECTION.POSITION_THRESHOLD) {
        positionTipsText.value = centerX < idealCenterX ? '请向右移动' : '请向左移动'
        return null
      }
      if (Math.abs(centerY - idealCenterY) > CONFIG.FACE_DETECTION.POSITION_THRESHOLD) {
        positionTipsText.value = centerY < idealCenterY ? '请向下移动' : '请向上移动'
        return null
      }
      if (box.width < displaySize.width * CONFIG.FACE_DETECTION.MIN_FACE_RATIO) {
        positionTipsText.value = '请再靠近一些'
        return null
      }
      if (box.width > displaySize.width * CONFIG.FACE_DETECTION.MAX_FACE_RATIO) {
        positionTipsText.value = '请后退一些'
        return null
      }

      // 位置合适，清除提示
      showPositionTips.value = false
      positionTipsText.value = ''

      // 创建新的canvas来截取人脸区域
      const faceCanvas = document.createElement('canvas')
      const padding = CONFIG.FACE_DETECTION.PADDING
      faceCanvas.width = box.width + padding * 2
      faceCanvas.height = box.height + padding * 2
      const faceContext = faceCanvas.getContext('2d')

      if (faceContext) {
        // 从原始画布截取人脸区域，并考虑padding
        faceContext.drawImage(
          canvas,
          Math.max(0, box.x - padding),
          Math.max(0, box.y - padding),
          box.width + padding * 2,
          box.height + padding * 2,
          0,
          0,
          faceCanvas.width,
          faceCanvas.height
        )
        return { result, canvas: faceCanvas }
      }
      return { result, canvas }
    }

    showPositionTips.value = true
    positionTipsText.value = '未检测到人脸'
    return null
  },

  async uploadAndSearch(imageDataUrl: string): Promise<ApiResponse<CheckInResult>> {
    const blob = await fetch(imageDataUrl).then((res) => res.blob())
    const formData = new FormData()
    formData.append('file', blob, 'face.jpg')

    const response = await request({
      url: apiUrl.value,
      method: 'post',
      data: formData,
      headers: {
        'Content-Type': 'multipart/form-data',
      },
    })

    const ossResult = response as any
    console.log('OSS上传响应:', ossResult)

    if (!ossResult?.base64Image) {
      throw new Error('图片上传失败')
    }

    const activityId = route.query.activeId
    if (!activityId || Array.isArray(activityId)) {
      throw new Error('活动ID无效')
    }

    return await faceCheckIn({
      activityId,
      image: ossResult.base64Image,
    })
  },
}

// 状态管理相关函数
const stateUtils = {
  resetStatus() {
    console.log('重置识别状态...')
    status.value = 'idle'
    recognitionThrottle()
  },

  updateStatus(newStatus: typeof status.value, message?: string, playSound: boolean = false) {
    status.value = newStatus
    if (message) {
      identifyMsg.value = message
      // 如果是成功状态且需要播放音效，则播放
      if (newStatus === 'success' && playSound) {
        playSuccessSound()
      }
      // 如果是成功或错误状态，1秒后重置提示文本
      if (newStatus === 'success' || newStatus === 'error') {
        setTimeout(() => {
          identifyMsg.value = ''
          status.value = 'idle'
        }, 1000)
      }
    }
  },
}

// 音频播放函数
const playSuccessSound = () => {
  successAudio.currentTime = 0 // 重置音频播放位置
  successAudio.play().catch((error) => {
    console.error('音频播放失败:', error)
  })
}

// 当视频开始播放时，进行识别
const onVideoPlaying = () => {
  console.log('视频开始播放...')
  if (video.value?.videoWidth && video.value?.videoHeight && video.value.videoWidth > 0 && video.value.videoHeight > 0) {
    console.log(`视频宽度: ${video.value.videoWidth}, 高度: ${video.value.videoHeight}`)
    recognitionThrottle()
  } else {
    console.log('视频尺寸无效，等待视频加载完成...')
  }
}

// 主要识别流程
const recognizeFace = async () => {
  if (!isInitialized.value || !video.value || !video.value.srcObject) {
    console.log('模型未初始化或视频流不可用，跳过识别')
    return
  }

  try {
    const detection = await faceRecognitionUtils.detectFace(video.value)

    if (!detection) {
      showFacePhoto.value = false
      recognitionThrottle()
      return
    }

    const { canvas } = detection
    const imageDataUrl = canvas.toDataURL()
    faceImage.value = imageDataUrl
    showFacePhoto.value = true

    const activityId = route.query.activeId
    if (!activityId || Array.isArray(activityId)) {
      throw new Error('活动ID无效')
    }

    const response: ApiResponse<CheckInResult> = await faceCheckIn({
      activityId,
      image: imageDataUrl.split(',')[1],
    })

    if (response.code === 0) {
      userName.value = response.result?.name || ''
      stateUtils.updateStatus('success', `签到成功`, true)
    } else if (response.code === -1 && response.message === '已签到') {
      stateUtils.updateStatus('success', `签到成功`, false)
    } else {
      stateUtils.updateStatus('error', '无权签到')
    }
    recognitionThrottle()
  } catch (error) {
    console.error('识别失败:', error)
    showFacePhoto.value = false
    stateUtils.updateStatus('error', '无权签到')
    recognitionThrottle()
  }
}

// 生命周期
onMounted(async () => {
  try {
    await faceRecognitionUtils.initialize()
    await cameraUtils.start()
  } catch (error) {
    console.error('组件初始化失败:', error)
    stateUtils.updateStatus('error', '初始化失败，请刷新页面重试')
  }
})

onBeforeUnmount(() => {
  cameraUtils.stop()
  recognitionThrottle.cancel()
  recognitionDebounce.cancel()
})
</script>

<style scoped lang="scss">
.main-body {
  position: relative;
}

.company-info {
  position: absolute;
  top: 20px;
  right: 20px;
  display: flex;
  align-items: center;
  gap: 10px;
  z-index: 1000;
}

.company-logo {
  width: 40px;
  height: 40px;
  object-fit: contain;
}

.company-name {
  color: #333;
  font-size: 16px;
  font-weight: 500;
}

.video-container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 100%;
  position: relative;

  .radiusDiv {
    width: 400px;
    height: 400px;
    border-radius: 50%;
  }
}

.tips-container {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  margin-top: 20px;
}

.position-tips {
  color: white;
  padding: 8px 16px;
  border-radius: 4px;
  font-size: 16px;
  margin-bottom: 10px;
  text-align: center;
  position: absolute;
  z-index: 10;
  bottom: -3em;
  left: 0;
  right: 0;
}

.tips-box {
  font-size: 2.5em;
  font-weight: bold;
  color: #d23e3e;
  text-align: center;
  transition: margin-top 0.3s ease;
  min-height: 60px;
  display: flex;
  align-items: center;
  justify-content: center;

  &.with-position-tips {
    margin-top: 10px;
  }
}

.floating-box {
  position: fixed;
  top: 20px;
  right: 20px;
  background-color: rgba(0, 0, 0, 0.7);
  padding: 10px;
  border-radius: 8px;
  z-index: 1000;
}

.face-photo {
  width: 150px;
  height: 150px;
  object-fit: cover;
  border-radius: 4px;
  border: 2px solid #fff;
}

.debug-btn {
  display: none;
}

.face-guide-box {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 300px;
  height: 300px;
  border: 2px dashed rgba(255, 255, 255, 0.5);
  border-radius: 50%;
  pointer-events: none;
}

.guide-text {
  position: absolute;
  width: 100%;
  text-align: center;
  bottom: -30px;
  color: #666;
  font-size: 14px;
}

// 添加过渡动画
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>
